package xh.player;


import org.apache.commons.io.IOUtils;
import xh.util.Global;
import xh.util.MusicStore;

import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;

/**
 * Created by Administrator on 2016/6/25.
 */
public class BasicPlayer {

    private Playing playing;

    private Thread playingThread;

    private StatusChangeListener statusChangeListener;

    private Status st = Status.Ready;

    private int volume = 50; // [-100 - +100]

    private static final int VolumeLow = -100;

    private static final int VolumeHigh = 100;


    public synchronized void play(URL url) {

        if (playing != null && playingThread.isAlive() && playing.getFile().equals(url)) {
            return;
        }

        /**
         * 这里必须保证单线程播放，所以要阻塞等待上一首停止
         */
        stop(); // wait for stop

        if (st == Status.Stopped || st == Status.Ready) {
            playing = new Playing(url);
            playingThread = new Thread(playing, "t-player-" + Global.rand(10000));

            // 一定要在父线程中把状态设置为playing，
            // 否则第二个请求进来时 playing 状态可能还是 stop
            // 将导致多个播放线程
            st = Status.Playing;
            playingThread.start();
        }
    }

    public synchronized void restart() {
        if (st == Status.Pause) {
            if (playing != null) {
                playing.restart();
            }
        }
    }

    public synchronized void pause() {
        if (st == Status.Playing) {
            if (playing != null) {
                playing.pause();
            }
        }
    }

    public synchronized void stop() {
        if (playingThread != null) {

            if (playingThread.isAlive()) {
                playing.stop();
            }

            while (true) {
                if (st != Status.Stopped) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                    }
                } else {
                    break;
                }
            }
        }
    }

    public PlayerStatusInfo statusInfo() {
        PlayerStatusInfo info = new PlayerStatusInfo();
        if (playing != null) {
            if (playing.getFile() != null) {
                try {
                    info.setPlayerStatus(st);
                    info.setPlayingMusicName(URLDecoder.decode(new File(playing.getFile().getFile()).getName(), Global.DefaultCharset.toString()));

                    info.setCurSecond((int) (playing.line.getMicrosecondPosition() / 1000000));
                    info.setTotalSecond(playing.mp3FileInfo.getTimeLength());
                } catch (UnsupportedEncodingException e) {
                }
            }
        }
        return info;
    }

    public void adjustVolume(int v) {

        int t = volume + v;
        if (t > VolumeHigh) {
            volume = VolumeHigh;
        } else if (t < VolumeLow) {
            volume = VolumeLow;
        } else {
            volume = t;
        }

        if (playing != null) {
            playing.adjustVolume();
        }
    }

    public URL getPlaying() {
        if (playing == null) {
            return null;
        } else {
            return playing.getFile();
        }
    }

    public boolean isPlaying() {
        if (playing == null) {
            return false;
        }

        return Status.Stopped != st;
    }

    public Status playerStatus() {
        return st;
    }

    public StatusChangeListener getStatusChangeListener() {
        return statusChangeListener;
    }

    private void fireListener() {
        if (statusChangeListener != null) {
            statusChangeListener.onChange(statusInfo());
        }
    }

    public void setStatusChangeListener(StatusChangeListener statusChangeListener) {
        this.statusChangeListener = statusChangeListener;
    }

    public interface StatusChangeListener {
        void onChange(PlayerStatusInfo playerStatusInfo);
    }

    private class Playing implements Runnable {

        private boolean isPause = false;

        private boolean stop = false;

        private URL file;

        private Object waitOnObject = new Object();

        private FloatControl fc;

        private SourceDataLine line;

        private MusicStore.MP3FileInfo mp3FileInfo;

        public Playing(URL file) {
            this.file = file;
        }

        private void adjustVolume() {
            if (fc != null) {
                final float min = fc.getMinimum();
                final float max = fc.getMaximum();

                float vc = (float) (volume - VolumeLow) / (VolumeHigh - VolumeLow) * (max - min) + min;
                fc.setValue(vc);

                System.out.println("current fc value: " + vc + ", volume: " + volume);
            }
        }


        public void run() {
            System.out.println("[player] start play: " + file.getPath());

            AudioInputStream in = null;
            try {
                in = AudioSystem.getAudioInputStream(file);

                mp3FileInfo = MusicStore.mp3FileInfo(new File(file.toURI()));

                final AudioFormat outFormat = getOutFormat(in.getFormat());
                final DataLine.Info info = new DataLine.Info(SourceDataLine.class, outFormat);

                line = (SourceDataLine) AudioSystem.getLine(info);

                line.open(outFormat);

                fc = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN);

                adjustVolume();

                // after preparation, invoke status change
                fireListener();

                line.start();

                AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(outFormat, in);

                loop(audioInputStream, line);


                line.drain();
                line.stop();


            } catch (Exception e) {
                throw new IllegalStateException(e);
            } finally {
                IOUtils.closeQuietly(in);
                st = Status.Stopped;
                fireListener();
            }
        }

        private void loop(AudioInputStream in, SourceDataLine line) throws IOException {
            while (!stop) {

                while (isPause) {
                    if (st == Status.Playing) {
                        st = Status.Pause;
                        fireListener();
                    }
                    st = Status.Pause;
                    synchronized (waitOnObject) {
                        try {
                            waitOnObject.wait(5000);
                        } catch (InterruptedException e) {
                        }
                    }
                }

                if (st == Status.Pause) {
                    st = Status.Playing;
                    fireListener();
                }
                st = Status.Playing;
                if (!stream(in, line)) {
                    break;
                }
            }
        }


        private AudioFormat getOutFormat(AudioFormat inFormat) {
            final int ch = inFormat.getChannels();
            final float rate = inFormat.getSampleRate();
            return new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, 16, ch, ch * 2, rate, false);
        }

        private boolean stream(AudioInputStream in, SourceDataLine line)
                throws IOException {
            final byte[] buffer = new byte[1024 * 4];
            int n = in.read(buffer, 0, buffer.length);

            if (n == -1) {
                return false;
            }

            line.write(buffer, 0, n);

            return true;
        }


        public void restart() {
            isPause = false;
            synchronized (waitOnObject) {
                waitOnObject.notifyAll();
            }
        }


        public void pause() {
            isPause = true;
        }

        public void stop() {
            stop = true;
            isPause = false;
            synchronized (waitOnObject) {
                waitOnObject.notifyAll();
            }
        }

        public URL getFile() {
            return file;
        }
    }

}
